﻿using System;
using System.Windows;
using System.Windows.Shapes;
using Microsoft.Popfly.GameCreator.GameEngine.Events;

namespace Microsoft.Popfly.GameCreator.GameEngine.Collisions
{
    public static class CollisionMath
    {
        // Helps add sides to the result for use by collision handlers (for instance, to enable jumping)
        private static void CircleOverlapHelper(CollisionDirectionArg result)
        {
            // determine the top, bottom, left and right overlaps of circle2 by circle1
            if (result.X > 0.0)
            {
                result.Right = result.X;
                if (result.X >= Math.Abs(result.Y))
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                }
            }
            else if (result.X == 0.0)
            {
                // Don't set right or left because we're not overlapping in either direction
            }
            else
            {
                result.Left = -result.X;
                if (result.Left >= Math.Abs(result.Y))
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                }
            }

            if (result.Y > 0.0)
            {
                result.Bottom = result.Y;
                if (result.Y > Math.Abs(result.X))
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                }
            }
            else if (result.Y == 0.0)
            {
                // Don't set either since we're not overlapping in either direction.
            }
            else
            {
                result.Top = -result.Y;
                if (result.Top > Math.Abs(result.X))
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                }
            }
        }

        private static CollisionDirectionArg CornerHelper(
            Point center, 
            Point corner, 
            ref double distSq, 
            ref double distance,
            double fudgeFactor,
            ref double radiusSquared,
            ref double radius
            )
        {
            distSq = MathUtils.DistanceSquared(center, corner); 
            
            if (distSq < radiusSquared)
            {
                distance = Math.Sqrt(distSq) + fudgeFactor; // making it just short of reaching the corner to make sure we move just outside
            
                Point fromEdge = MathUtils.Subtract(center, corner);
            
                // this is the vector we should move the square to get just out of the circle
                // if the square is the thing that should move, it should move the opposite of this
                if (fromEdge.X == 0 && fromEdge.Y == 0)
                {
                    // uh oh, the center is exactly on the corner.  Since this can only happen when we're on the 
                    // bottom right corner, let's move the square up and left by saying the center is 1 right and 1 down
                    // from corner and recalc fromEdge
                    fromEdge = MathUtils.Subtract(new Point(1, 1), new Point(0, 0));
                }
                Point cornerResultPoint = MathUtils.Scale(MathUtils.Normalize(fromEdge), radius - distance);

                CollisionDirectionArg cornerResult = new CollisionDirectionArg();
                cornerResult.X = cornerResultPoint.X;
                cornerResult.Y = cornerResultPoint.Y;

                if (Math.Abs(cornerResult.X) >= Math.Abs(cornerResult.Y))
                {
                    if (cornerResult.X > 0)
                        cornerResult.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                    else
                        cornerResult.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                }
                else
                {
                    if (cornerResult.Y > 0)
                        cornerResult.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                    else
                        cornerResult.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                }
                return cornerResult;
            }
            else 
            {
                return null;
            }
        }

        private static CollisionDirectionArg SideHelper(
            Point center,
            double radius,
            CollisionBounds rect,
            double fudgeFactor,
            double smallEdge,
            double bigEdge,
            char moveDirection,
            char stationaryDirection
            )
        {
            double distTop;
            double distBottom;

            if (moveDirection == 'X')
            {
                distTop = Math.Abs(rect.X - center.X);
                distBottom = Math.Abs(center.X - bigEdge);
            }
            else
            {
                distTop = Math.Abs(rect.Y - center.Y);
                distBottom = Math.Abs(center.Y - bigEdge);
            }

            CollisionDirectionArg result = new CollisionDirectionArg();
            if (distTop < distBottom)
            {
                if (distTop < radius)
                {
                    double moveResult = -radius + distTop - fudgeFactor;
                    if (moveDirection == 'X')
                    {
                        result.X = moveResult;
                        result.Y = 0;
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                    }
                    else
                    {
                        result.Y = moveResult;
                        result.X = 0;
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                    }
                    return result; // move the rect in the positive
                }
                else
                {
                    return null;
                }
            }
            else
            {
                if (distBottom <= radius)
                {
                    double moveResult = -distBottom + radius + fudgeFactor;
                    if (moveDirection == 'X')
                    {
                        result.X = moveResult;
                        result.Y = 0;
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                    }
                    else
                    {
                        result.X = 0;
                        result.Y = moveResult;
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                    }
                    return result; // move the rect in the negative
                }
                else
                {
                    return null;
                }
            }
        }

        public static CollisionDirectionArg OverlapCircleRect(
            System.Windows.Point center, 
            double radius,
            CollisionBounds rect
            )
        {
            double radiusSquared = radius * radius;
    
            double distSq = radiusSquared + 10.0; // just make sure we are bigger than radius squared
    
            double distance = 0.0;
    
            double fudgeFactor = 0.00001;
    
            double rightEdge = rect.X + rect.Width;
            double bottomEdge = rect.Y + rect.Height;
    
            CollisionDirectionArg result = null;

            // Closest point is the upper left corner
            if (center.X < rect.X && center.Y < rect.Y)
            {
                result = CornerHelper(center, new Point(rect.X, rect.Y), ref distSq, ref distance, fudgeFactor, ref radiusSquared, ref radius);
            }
            // Closest point is the bottom left corner
            else if (center.X < rect.X && center.Y >= bottomEdge)
            {
                result = CornerHelper(center, new Point(rect.X, bottomEdge), ref distSq, ref distance, fudgeFactor, ref radiusSquared, ref radius);
            }
            // Closest point is the upper right corner
            else if (center.X >= rightEdge && center.Y < rect.Y)
            {
                result = CornerHelper(center, new Point(rightEdge, rect.Y), ref distSq, ref distance, fudgeFactor, ref radiusSquared, ref radius);
            }
            // Closest point is the lower right corner
            else if (center.X >= rightEdge && center.Y >= bottomEdge)
            {
                result = CornerHelper(center, new Point(rightEdge, bottomEdge), ref distSq, ref distance, fudgeFactor, ref radiusSquared, ref radius);
            }
            // Center is inside the rect
            else if (center.X >= rect.X && center.X < rightEdge &&
                     center.Y >= rect.Y && center.Y < bottomEdge)
            {
                // Find the closest edge - corners will always be further than an edge if we're inside the square.
                double distLeft = center.X - rect.X;
                double distRight = rightEdge - center.X;
                double distTop = center.Y - rect.Y;
                double distBottom = bottomEdge - center.Y;
        
        
                double minDist = Math.Min(distLeft, distRight);
                minDist = Math.Min(minDist, distTop);
                minDist = Math.Min(minDist, distBottom);
        
        
                // Calculate the vector from the closest edge toward the center which will
                // get the rect just out of the circle (the fudgeFactor will hopefully avoid
                // the actors still colliding due to FP errors.
                if (minDist == distLeft)
                {
                    result = new CollisionDirectionArg();
                    result.X = -(distLeft + radius + fudgeFactor);
                    result.Y = 0.0;
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                }
                else if (minDist == distRight)
                {
                    result = new CollisionDirectionArg();
                    result.X = (distRight + radius + fudgeFactor);
                    result.Y = 0.0;
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                }
                else if (minDist == distTop)
                {
                    result = new CollisionDirectionArg();
                    result.X = 0.0;
                    result.Y = -(distTop + radius + fudgeFactor);
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                }
                else if (minDist == distBottom)
                {
                    result = new CollisionDirectionArg();
                    result.X = 0.0;
                    result.Y = (distBottom + radius + fudgeFactor);
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                }
                else
                {
                    // This should never be hit.
                    System.Diagnostics.Debug.Assert(false);
                    return null;
                }
            }
            // center is in between right and left edges of the rect, so closest point is top or bottom of rect
            else if (center.X >= rect.X && center.X < rightEdge)
            {
                result = SideHelper(center, radius, rect, fudgeFactor, rect.Y, bottomEdge, 'Y', 'X');
            }
            // Center is between top and bottom edges of the rect, so closest point is right or left edge of rect
            else if (center.Y >= rect.Y && center.Y < bottomEdge)
            {
                result = SideHelper(center, radius, rect, fudgeFactor, rect.X, rightEdge, 'X', 'Y');
            }

            if (result == null)
                return null;

            CircleOverlapHelper(result);

            return result;
        }

        public static CollisionDirectionArg OverlapRectRect(
            CollisionBounds rect1,
            CollisionBounds rect2,
            BehaviorOwner actor1,
            BehaviorOwner actor2
            )
        {
            if (rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
                rect1.Y >= rect2.Y + rect2.Height || rect1.Y + rect1.Height <= rect2.Y)
            {
                return null;
            }

            // We know they overlap.  Now figure out what edges of rect2 (if any) are overlapped by rect1, and by how much.
            CollisionDirectionArg result = new CollisionDirectionArg();

            if (rect1.Y + rect1.Height > rect2.Y && rect1.Y < rect2.Y)
                result.Top = rect1.Y + rect1.Height - rect2.Y;
            if (rect1.Y + rect1.Height > rect2.Y + rect2.Height && rect1.Y < rect2.Y + rect2.Height)
                result.Bottom = rect2.Y + rect2.Height - rect1.Y;
            if (rect1.X + rect1.Width > rect2.X && rect1.X < rect2.X)
                result.Left = rect1.X + rect1.Width - rect2.X;
            if (rect1.X + rect1.Width > rect2.X + rect2.Width && rect1.X < rect2.X + rect2.Width)
                result.Right = rect2.X + rect2.Width - rect1.X;

            // overlap both which cancels, so do top or bottom   
            if (result.Right != 0.0 && result.Left != 0.0)
            {
                if (result.Top != 0.0)
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                else
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
            }
            else if (result.Top != 0.0 && result.Bottom != 0.0)
            {
                if (result.Left != 0.0)
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                else
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
            }
            else if (result.Right != 0.0) // no double overlaps, so check right
            {
                if (result.Top != 0.0) // if top is set too, choose the lesser
                {
                    if (Math.Abs(result.Right) <= Math.Abs(result.Top))
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                    else
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                }
                else if (result.Bottom != 0.0) // if bottom is set too, choose the lesser
                {
                    if (Math.Abs(result.Right) <= Math.Abs(result.Bottom))
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                    else
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                }
                else
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                }
            }
            else if (result.Left != 0.0) // no doubled sides and no right, so check left
            {
                if (result.Top != 0.0)
                {
                    if (Math.Abs(result.Left) <= Math.Abs(result.Top))
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                    else
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                }
                else if (result.Bottom != 0.0)
                {
                    if (Math.Abs(result.Left) <= Math.Abs(result.Bottom))
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                    else
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                }
                else
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                }
            }
            else if (result.Top != 0.0) // no doubled sides, no left or right, so do top or bottom
            {
                result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
            }
            else if (result.Bottom != 0.0)
            {
                result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
            }
            else // Nothing, so use velocity
            {
                double xVel = actor1.XVelocity - actor2.XVelocity;
                double yVel = actor1.YVelocity - actor2.YVelocity;
                if (xVel != 0.0 && yVel != 0.0)
                {
                    // Now use the bigger value to determine the direction
                    if (Math.Abs(xVel) >= Math.Abs(yVel))
                    {
                        if (xVel > 0)
                            result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                        else
                            result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;

                    }
                    else
                    {
                        if (yVel > 0)
                            result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                        else
                            result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                    }
                }
                else if (xVel != 0)
                {
                    if (xVel > 0)
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                    else
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                }
                else if (yVel != 0)
                {
                    if (yVel > 0)
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                    else
                        result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                }
                else
                {
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_NONE;
                }
            }

            return result;
        }

        public static CollisionDirectionArg OverlapCircleCircle(Point center1, double radius1, Point center2, double radius2)
        {
            Point twoToOne = MathUtils.Subtract(center1, center2);
            double distanceSquared = MathUtils.MagnitudeSquared(twoToOne);
            double radii = radius1 + radius2;
            double radiiSquared = radii * radii;
            if (distanceSquared > radiiSquared)
            {
                return null; // they don't overlap
            }
            else
            {
                double distance = radii - Math.Sqrt(distanceSquared) + 0.00001;
                CollisionDirectionArg result = new CollisionDirectionArg();
                if (center1.Equals(center2))
                {
                    // They are right on top of each other, so pick an arbitrary direction to move distance
                    result.X = distance;
                    result.Y = 0;
                }
                else
                {
                    Point resultPoint = MathUtils.Scale(MathUtils.Normalize(twoToOne), distance);
                    result.X = resultPoint.X;
                    result.Y = resultPoint.Y;
                }

                // determine the top, bottom, left and right overlaps of circle2 by circle1
                CircleOverlapHelper(result);
                return result;
            }
        }

        public static CollisionDirectionArg OverlapRectCircle(CollisionBounds rect, Point center, double radius)
        {
            CollisionDirectionArg result = OverlapCircleRect(center, radius, rect);
            if (result == null)
            {
                return null;
            }
    
            result.X = -result.X;
            result.Y = -result.Y;
    
            CollisionDirectionArg reverse = new CollisionDirectionArg();
            
            reverse.Right = result.Left;
            reverse.Left = result.Right;
            reverse.Bottom = result.Top;
            reverse.Top = result.Bottom;
        
            result.Right = reverse.Right;
            result.Left = reverse.Left;
            result.Bottom = reverse.Bottom;
            result.Top = reverse.Top;
    
            switch (result.FilterDirection)
            {
                case CollisionDirectionArg.FilterDirections.FILTER_TOP:
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_BOTTOM;
                    break;
                case CollisionDirectionArg.FilterDirections.FILTER_BOTTOM:
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_TOP;
                    break;
                case CollisionDirectionArg.FilterDirections.FILTER_LEFT:
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_RIGHT;
                    break;
                case CollisionDirectionArg.FilterDirections.FILTER_RIGHT:
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_LEFT;
                    break;
                case CollisionDirectionArg.FilterDirections.FILTER_NONE:
                    result.FilterDirection = CollisionDirectionArg.FilterDirections.FILTER_NONE;
                    break;
            }
    
            return result;
        }

        public static CollisionDirectionArg OverlapActorActor(Actor actor1, Actor actor2)
        {
            CollisionBounds actor1Bounds = actor1.GetBounds();
            CollisionBounds actor2Bounds = actor2.GetBounds();

            if (actor1.CurrentState.Edges.Type == CollisionEdges.EdgesType.CIRCLE &&
                actor2.CurrentState.Edges.Type == CollisionEdges.EdgesType.CIRCLE)
            {
                return OverlapCircleCircle(actor1.GetObjectCenter(), actor1Bounds.Radius, actor2.GetObjectCenter(), actor2Bounds.Radius); 
            }
            else if (actor1.CurrentState.Edges.Type == CollisionEdges.EdgesType.CIRCLE &&
                actor2.CurrentState.Edges.Type == CollisionEdges.EdgesType.RECT)
            {
                return OverlapCircleRect(actor1.GetObjectCenter(), actor1Bounds.Radius, actor2Bounds);
            }
            else if (actor1.CurrentState.Edges.Type == CollisionEdges.EdgesType.RECT &&
                actor2.CurrentState.Edges.Type == CollisionEdges.EdgesType.CIRCLE)
            {
                return OverlapRectCircle(actor1Bounds, actor2.GetObjectCenter(), actor2Bounds.Radius);
            }
            else
            {
                return OverlapRectRect(actor1Bounds, actor2Bounds, actor1, actor2);
            }
        }
    }
}
